package org.molamil.acto.handlers {
	import org.molamil.acto.core.ns.acto;
	import org.molamil.acto.core.events.ActoEvent;
	import org.molamil.acto.core.registry.RegisteredTarget;
	import org.molamil.acto.core.loggers.Logger;
	import org.molamil.acto.core.session.ActoSession;
	import org.molamil.acto.params.ParamContext;
	import org.molamil.acto.utils.ObjectUtils;
	import org.molamil.acto.utils.StringUtils;

	import flash.errors.IllegalOperationError;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.utils.getQualifiedClassName;

	/**
	 * @author Jorge Hernandez
	 */
	public class AbstractHandlersController extends EventDispatcher implements HandlersController {


		// CONSTANTS
		
		private static const log:Logger = Logger.getLogger("AbstractHandlersController");
		
		public static const ACTION_CALL:String = "call";
		
		
		// PROPERTIES
		
		acto static var sessionControllers:Object; // with session.id as key and this as value 
		
		protected var session:ActoSession;
		
		private var _handlers:Array; // of HandlerContext
		
		
		// CONSTRUCTOR
		
		public function AbstractHandlersController(session:ActoSession) {
			
			// Abstract check.
			if (getQualifiedClassName(this) == "org.molamil.acto.handlers::AbstractHandlersController")
            	throw new IllegalOperationError("AbstractHandlersController must be extended");
            	
            this.session = session;
            	
            _handlers = new Array();
            
            if (acto::sessionControllers == null)
            	acto::sessionControllers = new Object();
            if (acto::sessionControllers[session.id] == null)
	            acto::sessionControllers[session.id] = new Array();
            acto::sessionControllers[session.id].push(this);
            
		}
		
		
		// PUBLIC METHODS
		
		public static function getSessionHandlers(id:String):Array {
			var a:Array = new Array();
			for each (var controller:AbstractHandlersController in acto::sessionControllers[id])
				a = a.concat(controller.handlers);
			return a;
		}
		
		public static function getSessionControllers(id:String):Array {
			return acto::sessionControllers[id]; 
		}
		
		public function registerHandlerContext(handlerContext:HandlerContext):void {
			if (indexOfHandlerContext(handlerContext) == -1) {
				handlerContext.priority = resolvePriority(handlerContext);
				_handlers.push(handlerContext);
				session.root.addEventListener(handlerContext.type, centralHandler, false, handlerContext.priority, true);
			} else {
				log.warning("There is already a handler added for \"{id}\" with type \"{type}\"".replace("{id}", handlerContext.context.id).replace("{type}", handlerContext.type));
			}
		}
		
		public function unregisterHandlerContext(handlerContext:HandlerContext):void {
			if (indexOfHandlerContext(handlerContext) != -1) {
				_handlers.splice(_handlers.indexOf(handlerContext), 1);
			} else {
				log.warning("There is no handler added for \"{id}\" with type \"{type}\"".replace("{id}", handlerContext.context.id).replace("{type}", handlerContext.type));
			}
		}
		
		public function unregisterAllHandlerContexts():void {
			_handlers = new Array();
		}
		
		public function resolveAction(handlerContext:HandlerContext, params:Object = null):void {
			log.severe("resolveAction() must be overriden by the AbstractHandlersController subclass: " + getQualifiedClassName(this));
		}
		
		public function resolvePriority(handlerContext:HandlerContext):int {
			return handlerContext.priority;
		}

		
		// PROTECTED METHODS
		
		protected function getFilteredParams(handlerContext:HandlerContext, event:Event):Object {
			var o:Object = new Object();
			for each (var paramContext:ParamContext in handlerContext.params) {
				if (paramContext.value != null && paramContext.value != "") {
					o[paramContext.name] = paramContext.value;
				} else if (ObjectUtils.containsInstancePropertyOrAccessor(event, paramContext.name)) {
					o[paramContext.name] = event[paramContext.name];
				} else {
					log.warning("There is no value to be passed for the event \"{event}\" on the parameter \"{param}\"".replace("{event}", handlerContext.type).replace("{param}", paramContext.name));
				}
			}
			return o;
		}
		
		protected function matchSource(handlerContext:HandlerContext, event:Event):Boolean {
			var b:Boolean = false;
			if (handlerContext.source != null && StringUtils.trim(handlerContext.source) != null) {
				var registeredTarget:RegisteredTarget;
				if (event is ActoEvent) {
					registeredTarget = session.targetRegistry.getRegisteredTarget(ActoEvent(event).targetObject);
				} else {
					registeredTarget = session.targetRegistry.getRegisteredTarget(event.target);
				}
				
				if (registeredTarget != null && registeredTarget.id == StringUtils.trim(handlerContext.source))
					b = true;
			} else {
				b = true;
			}	
			return b;
		}
		
		
		// EVENT HANDLERS
		
		public function centralHandler(event:Event):void {
			for each (var controller:AbstractHandlersController in acto::sessionControllers[session.id]) {
				var orderedHandlers:Array = new Array();
				for each (var handlerContext:HandlerContext in controller.handlers) {
					if (handlerContext.type == event.type && matchSource(handlerContext, event)) {
						var i:int;
						for (i = 0; i < orderedHandlers.length; i++) {
							if (HandlerContext(orderedHandlers[i]).priority < handlerContext.priority)
								break;
						}
						orderedHandlers.splice(i, 0, handlerContext);
					}
				}
				for each (handlerContext in orderedHandlers)
					controller.resolveAction(handlerContext, getFilteredParams(handlerContext, event));
			}
		}
		
		
		// PROTECTED METHODS
		
		protected function indexOfHandlerContext(handlerContext:HandlerContext):int {
			var index:int = -1;
			for (var i:int = 0; i < _handlers.length; i++) {
				var ec:HandlerContext = _handlers[i];
				if (handlerContext.type == ec.type && handlerContext.className == ec.className && handlerContext.action == ec.action && handlerContext.context == ec.context) {
					index = i;
					break;
				}
			}
			return index;
		}
		
		
		// GETTER/SETTER
		
		public function get handlers():Array {
			return _handlers;
		}
		
		
	}
	
}
